
function floorRef(currentFloor, type, row, col)
	if (type == "mob") then
        	local mobTable = currentFloor["mobTable"]
		if (mobTable[row] == nil or mobTable[row][col] == nil) then
			return nil
		else
			return mobTable[row][col]
		end
	else
	        local tileTable = currentFloor["tileTable"]
		if (tileTable[row] == nil or tileTable[row][col] == nil) then
			return nil
		else
			return tileTable[row][col]
		end
	end
end

function floorSet(currentFloor, type, row, col, newVal)
	if (type == "mob") then
        	local mobTable = currentFloor["mobTable"]
		if (mobTable[row] == nil) then
			mobTable[row] = {}
		end
		mobTable[row][col] = newVal
	else
	        local tileTable = currentFloor["tileTable"]
		if (tileTable[row] == nil) then
			tileTable[row] = {}
		end
		tileTable[row][col] = newVal
	end
end

function calculatePCStats(level, race, class)
	local statNames = {"maxHP", "maxMP", "melAtk", "rngAtk", "def", "splAtk", "splSup"}
	local result = {}
	for i, stat in pairs(statNames) do
		local levelFactor = level + 4
		local subtotalStat = levelFactor * raceStatFactors[race][stat] * classStatFactors[class][stat]
		local totalStat = math.min(100, math.floor(subtotalStat))
		result[stat] = totalStat
	end
	return result
end

function randInt(min, max)
	local scale = max - min + 1
	return math.floor(math.random() * scale) + min
end

function pickRandomTile(room)
	return randInt(room[1][1], room[2][1]), randInt(room[1][2], room[2][2])
end

function setSpecialRoom(c, stopAt, specialRooms, value)
	local j = 0
	for i=1,stopAt do
		if (specialRooms[i] == nil) then
			j = j + 1
		end
		if (j == c) then
			specialRooms[i] = { id = value }
			return i
		end
	end
end

function coordinatesWithinBoundary(xCheck, yCheck, x1, y1, x2, y2)
	return xCheck >= math.min(x1, x2)
		and xCheck <= math.max(x1, x2)
		and yCheck >= math.min(y1, y2)
		and yCheck <= math.max(y1, y2)
end

function coordinatesWithinRoom(xCheck, yCheck, room)
	for i, _ in pairs(room) do
		if (room[i+1] ~= nil) then
			if (coordinatesWithinBoundary(xCheck, yCheck, room[i][1], room[i][2], room[i+1][1], room[i+1][2])) then
				return true
			end
		end
	end
	return false
end

function calculateCenter(room)
	return math.floor((room[1][1] + room[2][1]) / 2.0), math.floor((room[1][2] + room[2][2]) / 2.0)
end

function calculateRoomDistance(room1, room2)
	local x1, y1 = calculateCenter(room1)
	local x2, y2 = calculateCenter(room2)
	local base = x2 - x1
	local height = y2 - y1
	return math.sqrt(base * base + height * height)
end

function findClosestRoom(room, listOfExistingRooms, skipRooms)
	local bestRoomIndex, bestRoomDistance
	bestRoomDistance = 10000000
	for i, checkRoom in pairs(listOfExistingRooms) do
		local distance = calculateRoomDistance(room, checkRoom)
		if (distance < bestRoomDistance and skipRooms[i] ~= nil) then
			bestRoomDistance = distance
			bestRoomIndex = i
		end
	end
	return bestRoomIndex
end

function calculateHallwayCoordinates(a1, b1, a4, b4, diffA)
	a2 = a1 + math.floor(diffA / 2)
	b2 = b1
	a3 = a2
	b3 = b4
	return a1, b1, a2, b2, a3, b3, a4, b4
end

function generateHallway(room1, room2)
	local room1CenterX, room1CenterY = calculateCenter(room1)
	local room2CenterX, room2CenterY = calculateCenter(room2)
	local xDiff = room2CenterX - room1CenterX
	local yDiff = room2CenterY - room1CenterY
	local isVertical = math.abs(xDiff) <= math.abs(xDiff)
	local x1, y1, x2, y2, x3, y3, x4, y4
	if (isVertical) then
		y1, x1, y2, x2, y3, x3, y4, x4 = calculateHallwayCoordinates(room1CenterY, room1CenterX, room2CenterY, room2CenterX, yDiff)
	else
		x1, y1, x2, y2, x3, y3, x4, y4 = calculateHallwayCoordinates(room1CenterX, room1CenterY, room2CenterX, room2CenterY, xDiff)
	end
	return { {x1, y1}, {x2, y2}, {x3, y3}, {x4, y4} }
end

function generateRoom(roomRange, listOfExistingRooms, minWidth, minHeight, maxWidth, maxHeight, xPadding, yPadding)
	local width = randInt(minWidth, maxWidth)
	local height = randInt(minHeight, maxHeight)
	local startX = randInt(roomRange[1], roomRange[3] - width)
	local startY = randInt(roomRange[2], roomRange[4] - height)
	for i, existingRoom in pairs(listOfExistingRooms) do
		local compareX = startX - existingRoom[2][1] + xPadding
		local compareY = startY - existingRoom[2][2] + yPadding
		if (compareX < 0 and compareY < 0) then
			if (compareX < compareY) then
				startX = existingRoom[2][1] + xPadding
			else
				startY = existingRoom[2][2] + yPadding
			end
		end				
	end
	return {{startX, startY}, {startX + width, startY + height}}
end

function generateTown(worldSeed)
	local townGenSeed = worldSeed + 20152314
	math.randomseed(townGenSeed)
	local result = {}
	for i=1,9 do
		result[i] = {}
		for j=1,9 do
			if (i == 1 or i == 9 or j == 1 or j == 9) then
				result[i][j] = 1
			elseif (i == 5 and j == 5) then
				result[i][j] = 11
			else
				result[i][j] = 0
			end
		end
	end
	return result
end

function generateFloor(worldSeed, floorNum)
	local floorGenSeed = worldSeed + 612151518 + floorNum
	math.randomseed(floorGenSeed)
	local xRoomSpacing = 4
	local yRoomSpacing = 4
	local xFloorPadding = 2
	local yFloorPadding = 2
	local roomWidth = 10
	local roomHeight = 10
	local innerRoomWidthMin = 3
	local innerRoomHeightMin = 3
	local innerRoomWidthMax = 8
	local innerRoomHeightMax = 8
	local innerRoomPaddingX = 2
	local innerRoomPaddingY = 2
	local roomRows = 4
	local roomCols = 4
	local roomRanges = {}
	for i=1,roomRows do
		roomRanges[i] = {}
		for j=1,roomCols do
			local x1 = (roomWidth + xRoomSpacing) * (j - 1)
			local y1 = (roomHeight + yRoomSpacing) * (i - 1)
			local x2 = x1 + roomWidth
			local y2 = y1 + roomHeight
			roomRanges[i][j] = {x1, y1, x2, y2}
		end
	end
	local listOfExistingRooms = {}
	local floorWidth = 0
	local floorHeight = 0
	for i=1,roomRows do
		for j=1,roomCols do
			local newRoom = generateRoom(roomRanges[i][j], listOfExistingRooms, innerRoomWidthMin, innerRoomHeightMin, innerRoomWidthMax, innerRoomHeightMax, innerRoomPaddingX, innerRoomPaddingY)
			table.insert(listOfExistingRooms, newRoom)
			floorWidth = math.max(floorWidth, newRoom[2][1])
			floorHeight = math.max(floorHeight, newRoom[2][2])
		end
	end
	floorWidth = floorWidth + xFloorPadding * 2
	floorHeight = floorHeight + yFloorPadding * 2

	local skipRooms = {}
	skipRooms[1] = true
	local hallways = {}

	for i=2,roomRows*roomCols do
		local j = findClosestRoom(listOfExistingRooms[i], listOfExistingRooms, skipRooms)
		table.insert(hallways, generateHallway(listOfExistingRooms[i], listOfExistingRooms[j]))
		skipRooms[i] = true
	end

	local specialRooms = {}
	local specialRoomCount = 0
	local result = {}
	if (floorNum < 100) then
		local c = randInt(1, roomRows*roomCols - specialRoomCount)
		local i = setSpecialRoom(c, roomRows*roomCols, specialRooms, 11)
		local x, y = pickRandomTile(listOfExistingRooms[i])
		specialRooms[i]["x"] = x
		specialRooms[i]["y"] = y
		specialRoomCount = specialRoomCount + 1
	end
	if (floorNum > 0) then
		local c = randInt(1, roomRows*roomCols - specialRoomCount)
		local i = setSpecialRoom(c, roomRows*roomCols, specialRooms, 12)
		local x, y = pickRandomTile(listOfExistingRooms[i])
		specialRooms[i]["x"] = x
		specialRooms[i]["y"] = y
		specialRoomCount = specialRoomCount + 1
	end
	if (floorNum % 5 == 1) then
		local c = randInt(1, roomRows*roomCols - specialRoomCount)
		local i = setSpecialRoom(c, roomRows*roomCols, specialRooms, 13)
		local x, y = pickRandomTile(listOfExistingRooms[i])
		specialRooms[i]["x"] = x
		specialRooms[i]["y"] = y
		specialRoomCount = specialRoomCount + 1
	end
	if (floorNum % 5 == 0) then
		local c = randInt(1, roomRows*roomCols - specialRoomCount)
		local i = setSpecialRoom(c, roomRows*roomCols, specialRooms, 14)
		local x, y = pickRandomTile(listOfExistingRooms[i])
		specialRooms[i]["x"] = x
		specialRooms[i]["y"] = y
		specialRoomCount = specialRoomCount + 1
	end

	local specialTiles = {}
	for i, room in pairs(specialRooms) do
		if (specialTiles[room["x"]] == nil) then
			specialTiles[room["x"]] = {}
		end
		specialTiles[room["x"]][room["y"]] = room["id"]
	end

	for i=1,floorHeight do
		result[i] = {}
		for j=1,floorWidth do
			local insideRoom = false
			if (specialTiles[j - xFloorPadding] ~= nil and specialTiles[j - xFloorPadding][i - yFloorPadding] ~= nil) then
				result[i][j] = specialTiles[j - xFloorPadding][i - yFloorPadding]
			else
				for _, room in pairs(listOfExistingRooms) do
					if (coordinatesWithinRoom(j - xFloorPadding, i - yFloorPadding, room)) then
						insideRoom = true
						break
					end
				end
				if (not insideRoom) then
					for _, hallway in pairs(hallways) do
						if (coordinatesWithinRoom(j - xFloorPadding, i - yFloorPadding, hallway)) then
							insideRoom = true
							break
						end
					end
				end
				if (insideRoom) then
					result[i][j] = 0
				else
					result[i][j] = 1
				end
			end
		end
	end
	return result
end

function getSpecialTile(type, floor)
	local codeList = {stairsUp = 11, stairsDown = 12, save = 13, boss = 14}
	local code = codeList[type]
	for i, row in pairs(floor["tileTable"]) do
		for j, cell in pairs(row) do
			if (cell == code) then
				return i, j
			end
		end
	end
end

moveableMobValues = {}
moveableMobValues[0] = true
moveableTileValues = {}
moveableTileValues[0] = true
moveableTileValues[11] = true
moveableTileValues[12] = true
moveableTileValues[13] = true

function changeFloors(globalState, changeType, targetFloor)
	local player = globalState["currentFloor"]["player"]
	local tileTable
	if (targetFloor == 0) then
		tileTable = generateTown(globalState["worldSeed"])
	else
		tileTable = generateFloor(globalState["worldSeed"], targetFloor)
	end
	local mobTable = {}

	local newFloor = {
		mobTable = mobTable,
		tileTable = tileTable,
		player = player,
		id = targetFloor
	}

	local playerRow, playerCol
	if (changeType == "up") then
		playerRow, playerCol = getSpecialTile("stairsDown", newFloor)
	elseif (changeType == "down") then
		playerRow, playerCol = getSpecialTile("stairsUp", newFloor)
	else
		playerRow, playerCol = getSpecialTile("save", newFloor)
	end
	player["row"] = playerRow
	player["col"] = playerCol

	floorSet(newFloor, "mob", player["row"], player["col"], 1)
	globalState["currentFloor"] = newFloor
end

function attemptToMove(globalState, direction)
	local currentFloor = globalState["currentFloor"]
	local mobTable = currentFloor["mobTable"]
	local tileTable = currentFloor["tileTable"]
	local player = currentFloor["player"]
	local playerRow, playerCol = player["row"], player["col"]
	local targetRow, targetCol
	if (direction == "up") then
		targetRow = playerRow - 1
		targetCol = playerCol
	elseif (direction == "down") then
		targetRow = playerRow + 1
		targetCol = playerCol
	elseif (direction == "left") then
		targetRow = playerRow
		targetCol = playerCol - 1
	else
		targetRow = playerRow
		targetCol = playerCol + 1
	end
	local targetMob = floorRef(currentFloor, "mob", targetRow, targetCol)
	local targetTile = floorRef(currentFloor, "tile", targetRow, targetCol)
	if ((targetMob == nil or moveableMobValues[targetMob]) and moveableTileValues[targetTile]) then
		executeRound(globalState, "move", targetRow, targetCol)
	elseif (targetMob ~= nil and targetMob > 0 and moveableTileValues[targetTile]) then
		executeRound(globalState, "attack", targetRow, targetCol)
	end
end

function executeTurn(globalState, mob, actionType, targetRow, targetCol)
	local currentFloor = globalState["currentFloor"]
	local mobTable = currentFloor["mobTable"]
	local mobRow = mob["row"]
	local mobCol = mob["col"]
	local id = mob["id"]
	if (actionType == "move") then
		floorSet(currentFloor, "mob", mobRow, mobCol, 0)
		floorSet(currentFloor, "mob", targetRow, targetCol, id)
		mob["row"] = targetRow
		mob["col"] = targetCol
	end
end

function executeRound(globalState, actionType, targetRow, targetCol)
	local currentFloor = globalState["currentFloor"]
	local mobTable = currentFloor["mobTable"]
	executeTurn(globalState, currentFloor["player"], actionType, targetRow, targetCol)
	for i, row in pairs(mobTable) do
		for j, cell in pairs(row) do
			if (cell > 1) then
				local mobLogic, targetRow, targetCol = getMobLogic(mob)
				executeTurn(globalState, cell, mobLogic, targetRow, targetCol)
			end
		end
	end
	if (actionType == "move") then
		local newTile = floorRef(currentFloor, "tile", targetRow, targetCol)
		if (newTile == 11) then
			changeFloors(globalState, "up", currentFloor["id"] + 1)
		elseif (newTile == 12) then
			changeFloors(globalState, "down", currentFloor["id"] - 1)
		end
	end
end

raceStatFactors = {
	human = { maxHP = 1.05, maxMP = 1.05, melAtk = 1.05, rngAtk = 1.05, def = 1.05, splAtk = 1.05, splSup = 1.05 }
}

classStatFactors = {
	warrior = { maxHP = 1.5, maxMP = 0.0, melAtk = 1.5, rngAtk = 1.25, def = 1.5, splAtk = 0.0, splSup = 0.0 }
}

-- below will be loaded from save
myClass = "warrior"
mySubclass = "knight"
myRace = "human"
mySubrace = "plains"
myLevel = 5
myExperience = 12
myBossLevelCode = 0
mySaveFloorCode = 1
myWorldSeed = 12345
-- above will be loaded from save

myStats = calculatePCStats(myLevel, myRace, myClass)
myStats["currentHP"] = myStats["maxHP"]
myStats["currentMP"] = myStats["maxMP"]

myPlayer = {
	stats = myStats,
	id = 1,
	row = 19,
	col = 19
}

myCurrentFloor = {
	player = myPlayer,
	id = 2
}

globalState = {
	worldSeed = myWorldSeed,
	currentFloor = myCurrentFloor
}

changeFloors(globalState, "up", 1)
